/**
 * Copyright (C) 2012 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/cfg_core.h"
#include "../../core/tcp_server.h"
#include "../../core/forward.h"

#include "msrp_env.h"
#include "msrp_netio.h"

/**
 *
 */
int msrp_forward(msrp_frame_t *mf, str *tpath, str *fpath)
{
	char fwdbuff[MSRP_MAX_FRAME_SIZE];
	msrp_env_t *env = msrp_get_env();
	char *buf;
	int len;

	if(tpath) {
		msrp_hdr_t *hdr;
		int maxlen;
		char *p;

		maxlen = mf->buf.len;

		if(fpath) {
			hdr = msrp_get_hdr_by_id(mf, MSRP_HDR_FROM_PATH);
			if(!hdr) {
				LM_ERR("From-Path header not found\n");
				return -1;
			}

			maxlen -= hdr->body.len;
			maxlen += fpath->len;
		}

		/* hdr must hold To-Path until we copy it bellow */
		hdr = msrp_get_hdr_by_id(mf, MSRP_HDR_TO_PATH);
		if(!hdr) {
			LM_ERR("To-Path header not found\n");
			return -1;
		}
		maxlen -= hdr->body.len;
		maxlen += tpath->len;

		if(maxlen >= MSRP_MAX_FRAME_SIZE - 1)
			return -1;

		p = fwdbuff;

		memcpy(p, mf->fline.buf.s, mf->fline.buf.len);
		p += mf->fline.buf.len;

		memcpy(p, "To-Path: ", 9);
		p += 9;
		memcpy(p, tpath->s, tpath->len);
		p += tpath->len;
		memcpy(p, "\r\n", 2);
		p += 2;

		memcpy(p, "From-Path: ", 11);
		p += 11;

		if(fpath) {
			memcpy(p, fpath->s, fpath->len);
			p += fpath->len;
			memcpy(p, "\r\n", 2);
			p += 2;
		} else {
			/* hdr must hold To-Path from above */
			memcpy(p, hdr->buf.s, hdr->buf.len);
			p += hdr->buf.len;
		}

		for(hdr = mf->headers; hdr; hdr = hdr->next) {
			if(hdr->htype == MSRP_HDR_TO_PATH
					|| hdr->htype == MSRP_HDR_FROM_PATH) {
				continue;
			}

			memcpy(p, hdr->buf.s, hdr->buf.len);
			p += hdr->buf.len;
		}

		if(mf->mbody.len > 0) {
			memcpy(p, "\r\n", 2);
			p += 2;

			memcpy(p, mf->mbody.s, mf->mbody.len);
			p += mf->mbody.len;
		}

		memcpy(p, mf->endline.s, mf->endline.len);
		p += mf->endline.len;

		buf = fwdbuff;
		len = p - fwdbuff;
	} else {
		buf = mf->buf.s;
		len = mf->buf.len;
	}

	if(!(env->envflags & MSRP_ENV_DSTINFO)
			&& (!tpath || msrp_env_set_dstinfo(mf, tpath, NULL, 0) < 0)) {
		LM_ERR("unable to set destination address\n");
		return -1;
	}

	if(unlikely((env->dstinfo.proto == PROTO_WS
						|| env->dstinfo.proto == PROTO_WSS)
				&& sr_event_enabled(SREV_TCP_WS_FRAME_OUT))) {
		struct tcp_connection *con = tcpconn_get(env->dstinfo.id, 0, 0, 0, 0);
		sr_event_param_t evp = {0};
		ws_event_info_t wsev;
		int ret;

		if(con == NULL) {
			LM_WARN("TCP/TLS connection for WebSocket could not be"
					"found\n");
			return -1;
		}

		memset(&wsev, 0, sizeof(ws_event_info_t));
		wsev.type = SREV_TCP_WS_FRAME_OUT;
		wsev.buf = buf;
		wsev.len = len;
		wsev.id = con->id;
		evp.data = (void *)&wsev;
		ret = sr_event_exec(SREV_TCP_WS_FRAME_OUT, &evp);
		tcpconn_put(con);
		return ret;
	} else if(tcp_send(&env->dstinfo, 0, buf, len) < 0) {
		LM_ERR("forwarding frame failed\n");
		return -1;
	}

	return 0;
}

/**
 *
 */
int msrp_send_buffer(str *buf, str *addr, int flags)
{
	return 0;
}

/**
 *
 */
int msrp_relay(msrp_frame_t *mf)
{
	struct dest_info *dst;
	struct tcp_connection *con = NULL;
	char reqbuf[MSRP_MAX_FRAME_SIZE];
	msrp_hdr_t *tpath;
	msrp_hdr_t *fpath;
	msrp_env_t *env;
	str_array_t *sar;
	char *p;
	char *l;
	int port;
	sr_event_param_t evp = {0};
	int ret;

	if(mf->buf.len >= MSRP_MAX_FRAME_SIZE - 1)
		return -1;

	tpath = msrp_get_hdr_by_id(mf, MSRP_HDR_TO_PATH);
	if(tpath == NULL) {
		LM_ERR("To-Path header not found\n");
		return -1;
	}
	fpath = msrp_get_hdr_by_id(mf, MSRP_HDR_FROM_PATH);
	if(fpath == NULL) {
		LM_ERR("From-Path header not found\n");
		return -1;
	}

	l = q_memchr(tpath->body.s, ' ', tpath->body.len);
	if(l == NULL) {
		LM_DBG("To-Path has only one URI -- nowehere to forward\n");
		return -1;
	}

	p = reqbuf;

	memcpy(p, mf->buf.s, tpath->body.s - mf->buf.s);
	p += tpath->body.s - mf->buf.s;

	memcpy(p, l + 1, fpath->body.s - l - 1);
	p += fpath->body.s - l - 1;

	memcpy(p, tpath->body.s, l + 1 - tpath->body.s);
	p += l + 1 - tpath->body.s;

	memcpy(p, fpath->name.s + 11, mf->buf.s + mf->buf.len - fpath->name.s - 11);
	p += mf->buf.s + mf->buf.len - fpath->name.s - 11;

	env = msrp_get_env();
	if(env->envflags & MSRP_ENV_DSTINFO) {
		dst = &env->dstinfo;
		goto done;
	}
	if(msrp_parse_hdr_to_path(mf) < 0) {
		LM_ERR("error parsing To-Path header\n");
		return -1;
	}
	sar = (str_array_t *)tpath->parsed.data;
	if(sar == NULL || sar->size < 2) {
		LM_DBG("To-Path has no next hop URI -- nowehere to forward\n");
		return -1;
	}
	if(msrp_env_set_dstinfo(mf, &sar->list[1], NULL, 0) < 0) {
		LM_ERR("unable to set destination address\n");
		return -1;
	}
	dst = &env->dstinfo;
done:
	if(dst->send_flags.f & SND_F_FORCE_CON_REUSE) {
		port = su_getport(&dst->to);
		if(likely(port)) {
			ticks_t con_lifetime;
			struct ip_addr ip;

			con_lifetime = cfg_get(tcp, tcp_cfg, con_lifetime);
			su2ip_addr(&ip, &dst->to);
			con = tcpconn_get(dst->id, &ip, port, NULL, con_lifetime);
		} else if(likely(dst->id)) {
			con = tcpconn_get(dst->id, 0, 0, 0, 0);
		}

		if(con == NULL) {
			LM_WARN("TCP/TLS connection not found\n");
			return -1;
		}

		if(unlikely((con->rcv.proto == PROTO_WS || con->rcv.proto == PROTO_WSS)
					&& sr_event_enabled(SREV_TCP_WS_FRAME_OUT))) {
			ws_event_info_t wsev;

			memset(&wsev, 0, sizeof(ws_event_info_t));
			wsev.type = SREV_TCP_WS_FRAME_OUT;
			wsev.buf = reqbuf;
			wsev.len = p - reqbuf;
			wsev.id = con->id;
			evp.data = (void *)&wsev;
			ret = sr_event_exec(SREV_TCP_WS_FRAME_OUT, &evp);
			tcpconn_put(con);
			return ret;
		} else if(tcp_send(dst, 0, reqbuf, p - reqbuf) < 0) {
			LM_ERR("forwarding frame failed\n");
			tcpconn_put(con);
			return -1;
		}

		tcpconn_put(con);
	} else if(tcp_send(dst, 0, reqbuf, p - reqbuf) < 0) {
		LM_ERR("forwarding frame failed\n");
		return -1;
	}

	return 0;
}

/**
 *
 */
int msrp_reply(msrp_frame_t *mf, str *code, str *text, str *xhdrs)
{
	char rplbuf[MSRP_MAX_FRAME_SIZE];
	msrp_hdr_t *hdr;
	msrp_env_t *env;
	char *p;
	char *l;
	sr_event_param_t evp = {0};
	int ret;

	/* no reply for a reply */
	if(mf->fline.msgtypeid == MSRP_REPLY)
		return 0;

	if(mf->fline.msgtypeid == MSRP_REQ_REPORT) {
		/* it does not take replies */
		return 0;
	}

	p = rplbuf;
	memcpy(p, mf->fline.protocol.s, mf->fline.protocol.len);
	p += mf->fline.protocol.len;
	*p = ' ';
	p++;
	memcpy(p, mf->fline.transaction.s, mf->fline.transaction.len);
	p += mf->fline.transaction.len;
	*p = ' ';
	p++;
	memcpy(p, code->s, code->len);
	p += code->len;
	*p = ' ';
	p++;
	memcpy(p, text->s, text->len);
	p += text->len;
	memcpy(p, "\r\n", 2);
	p += 2;
	memcpy(p, "To-Path: ", 9);
	p += 9;
	hdr = msrp_get_hdr_by_id(mf, MSRP_HDR_FROM_PATH);
	if(hdr == NULL) {
		LM_ERR("From-Path header not found\n");
		return -1;
	}
	if(mf->fline.msgtypeid == MSRP_REQ_SEND) {
		l = q_memchr(hdr->body.s, ' ', hdr->body.len);
		if(l == NULL) {
			memcpy(p, hdr->body.s, hdr->body.len + 2);
			p += hdr->body.len + 2;
		} else {
			memcpy(p, hdr->body.s, l - hdr->body.s);
			p += l - hdr->body.s;
			memcpy(p, "\r\n", 2);
			p += 2;
		}
	} else {
		memcpy(p, hdr->body.s, hdr->body.len + 2);
		p += hdr->body.len + 2;
	}
	hdr = msrp_get_hdr_by_id(mf, MSRP_HDR_TO_PATH);
	if(hdr == NULL) {
		LM_ERR("To-Path header not found\n");
		return -1;
	}
	memcpy(p, "From-Path: ", 11);
	p += 11;
	l = q_memchr(hdr->body.s, ' ', hdr->body.len);
	if(l == NULL) {
		memcpy(p, hdr->body.s, hdr->body.len + 2);
		p += hdr->body.len + 2;
	} else {
		memcpy(p, hdr->body.s, l - hdr->body.s);
		p += l - hdr->body.s;
		memcpy(p, "\r\n", 2);
		p += 2;
	}
	hdr = msrp_get_hdr_by_id(mf, MSRP_HDR_MESSAGE_ID);
	if(hdr != NULL) {
		memcpy(p, hdr->buf.s, hdr->buf.len);
		p += hdr->buf.len;
	}

	if(xhdrs != NULL && xhdrs->s != NULL) {
		memcpy(p, xhdrs->s, xhdrs->len);
		p += xhdrs->len;
	}

	memcpy(p, mf->endline.s, mf->endline.len);
	p += mf->endline.len;
	*(p - 3) = '$';

	env = msrp_get_env();

	if(unlikely((env->srcinfo.proto == PROTO_WS
						|| env->srcinfo.proto == PROTO_WSS)
				&& sr_event_enabled(SREV_TCP_WS_FRAME_OUT))) {
		struct tcp_connection *con = tcpconn_get(env->srcinfo.id, 0, 0, 0, 0);
		ws_event_info_t wsev;

		if(con == NULL) {
			LM_WARN("TCP/TLS connection for WebSocket could not be"
					"found\n");
			return -1;
		}

		memset(&wsev, 0, sizeof(ws_event_info_t));
		wsev.type = SREV_TCP_WS_FRAME_OUT;
		wsev.buf = rplbuf;
		wsev.len = p - rplbuf;
		wsev.id = con->id;
		evp.data = (void *)&wsev;
		ret = sr_event_exec(SREV_TCP_WS_FRAME_OUT, &evp);
		tcpconn_put(con);
		return ret;
	} else if(tcp_send(&env->srcinfo, 0, rplbuf, p - rplbuf) < 0) {
		LM_ERR("sending reply failed\n");
		return -1;
	}

	return 0;
}


/**
 *
 */
struct dest_info *msrp_uri_to_dstinfo(struct dns_srv_handle *dns_h,
		struct dest_info *dst, struct socket_info *force_send_socket,
		snd_flags_t sflags, str *uri)
{
	msrp_uri_t parsed_uri;
	str *host;
	int port;
	int ip_found;
	union sockaddr_union to;
	int err;

	init_dest_info(dst);

	if(msrp_parse_uri(uri->s, uri->len, &parsed_uri) < 0) {
		LM_ERR("bad msrp uri: %.*s\n", uri->len, uri->s);
		return 0;
	}

	if(parsed_uri.scheme_no == MSRP_SCHEME_MSRPS) {
		dst->proto = PROTO_TLS;
	} else {
		dst->proto = PROTO_TCP;
	}

	dst->send_flags = sflags;
	host = &parsed_uri.host;
	port = parsed_uri.port_no;

	if(dns_h && cfg_get(core, core_cfg, use_dns_failover)) {
		ip_found = 0;
		do {
			/* try all the ips until we find a good send socket */
			err = dns_sip_resolve2su(
					dns_h, &to, host, port, &dst->proto, dns_flags);
			if(err != 0) {
				if(ip_found == 0) {
					if(err != -E_DNS_EOR)
						LM_ERR("failed to resolve \"%.*s\" :"
							   "%s (%d)\n",
								host->len, ZSW(host->s), dns_strerror(err),
								err);
					return 0; /* error, no ip found */
				}
				break;
			}
			if(ip_found == 0) {
				dst->to = to;
				ip_found = 1;
			}
			dst->send_sock =
					get_send_socket2(force_send_socket, &to, dst->proto, 0);
			if(dst->send_sock) {
				dst->to = to;
				return dst; /* found a good one */
			}
		} while(dns_srv_handle_next(dns_h, err));
		LM_ERR("no corresponding socket for \"%.*s\" af %d\n", host->len,
				ZSW(host->s), dst->to.s.sa_family);
		/* try to continue */
		return dst;
	}

	if(sip_hostport2su(&dst->to, host, port, &dst->proto) != 0) {
		LM_ERR("failed to resolve \"%.*s\"\n", host->len, ZSW(host->s));
		return 0;
	}
	dst->send_sock =
			get_send_socket2(force_send_socket, &dst->to, dst->proto, 0);
	if(dst->send_sock == 0) {
		LM_ERR("no corresponding socket for af %d\n", dst->to.s.sa_family);
		/* try to continue */
	}
	return dst;
}

struct socket_info *msrp_get_local_socket(str *sockaddr)
{
	int port, proto;
	str host;
	char backup;
	struct socket_info *si;

	backup = sockaddr->s[sockaddr->len];
	sockaddr->s[sockaddr->len] = '\0';
	if(parse_phostport(sockaddr->s, &host.s, &host.len, &port, &proto) < 0) {
		LM_ERR("invalid socket specification\n");
		sockaddr->s[sockaddr->len] = backup;
		return NULL;
	}
	sockaddr->s[sockaddr->len] = backup;
	si = grep_sock_info(&host, (unsigned short)port, (unsigned short)proto);
	return si;
}
